﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.IO;
using System.Threading;
using System.Security.Principal;
using System.Security;
using System.Security.Permissions;
using System.Diagnostics;

namespace LabChecker.Models.Programs
{
    public class CppProgram : Program
    {
        //Наш словарь с исключениями
        private CppErrorDictionary _errorDictionary;

        private CompilationResultBase _wasCompiled;
        private string _tempDir;
        private string _tempPathToCpp;
        private string _tempPathToExe;
        private string _tempPathToBat;

        public CppProgram(string code)
            : base(code)
        {
            AddTempInfo(code);

            //Наш словарь с исключениями
            _errorDictionary = new CppErrorDictionary();
        }

        private void AddTempInfo(string code)
        {
            _tempDir = FileSystemService.CreateTempDirectory();
            _tempPathToCpp = Path.Combine(_tempDir, "proga.cpp");
            _tempPathToExe = Path.ChangeExtension(_tempPathToCpp, "exe");
            File.WriteAllText(_tempPathToCpp, code);
            _tempPathToBat = Path.Combine(_tempDir, "runcl.bat");

            var pathForVcvrsall = @"""C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\vcvarsall.bat""";
            var batText = "call " + pathForVcvrsall + @" & " + "cl.exe %1 /Fe%2";

            File.AppendAllText(_tempPathToBat, batText);
        }

        private bool IsCodeExists(string codeFile)
        {
            if (!File.Exists(codeFile))
            {
                throw new Exception("CppProgram.cs - Code do not exists");
            }

            return true;
        }

        public override CompilationResultBase Compile()
        {
            IsCodeExists(_tempPathToCpp);

            _wasCompiled = GetCompileResult(_tempPathToCpp);
            return _wasCompiled;
        }

        private void Prepare()
        {
            if (!(_wasCompiled is CompilationResult.Success))
            {
                throw new Exception("Doesn't compile");
            }
        }

        private string ReadCodeFromFile()
        {
            return File.ReadAllText(this.Code);
        }

        private CompilationResultBase GetCompileResult(string pathToCode)
        {
            //Process for CL.exe
            var cl = new Process
                             {
                                 StartInfo =
                                     {
                                         FileName = _tempPathToBat,
                                         Arguments = '"' + pathToCode + '"' + ' ' + '"' + _tempPathToExe + '"',
                                         CreateNoWindow = false,
                                         RedirectStandardOutput = true,
                                         RedirectStandardError = true,
                                         UseShellExecute = false
                                     }
                             };

            //Cl скомпилит в текущую Debug папку этого проекта
            cl.Start();

            //На случай ошибки - запишем инфу о ней в переменную
            var clErrCode = cl.StandardOutput.ReadToEnd();

            //В случае успешной компиляции Cl возвращает 0
            cl.WaitForExit();
            var clExitCode = cl.ExitCode;
            if (clExitCode != 0)
            {
                return new CompilationResult.Error(_errorDictionary.FindError(clExitCode) + clErrCode, 0, 0);
            }

            return new CompilationResult.Success();
        }

        public override string Run(string input)
        {

            //Проверим, было ли успешно скомпилировано
            Prepare();

            //ExeProc - процесс для запуска скомпилированного ехе-файла
            var exeProc = new Process();

            //Скомпилированный ехе-файл лежит в папке JIT-генерируемой папке temp - туда и прописываем путь

            exeProc.StartInfo.FileName = _tempPathToExe;

            exeProc.StartInfo.CreateNoWindow = false;//true - for final version
            exeProc.StartInfo.RedirectStandardOutput = true;
            exeProc.StartInfo.RedirectStandardInput = true;
            exeProc.StartInfo.RedirectStandardError = true;
            exeProc.StartInfo.UseShellExecute = false;
            exeProc.StartInfo.ErrorDialog = false;

            //Из БД получаем данные с сепаратором в виде ","
            //Для корректного ввода в программу изменяем на "."
            ConsoleIOSeparatorFix(ref input, ',', '.');

            try
            {
                int errCode = 0;
                exeProc.Start();
                exeProc.StandardInput.WriteLine(input);

                if (!exeProc.WaitForExit(3000))
                {
                    exeProc.Kill();
                    exeProc.Dispose();
                    throw new Exceptions.TimeLimitExceeded(3000);
                }

                errCode = exeProc.ExitCode;

                if (errCode == 0)
                {
                    //Console.WriteLine("All is good!");

                    //Из exe-файла получаем данные с сепаратором в виде "."
                    //В БД ответы с сепаратором в виде ","
                    //Для корректного сравнения поменяем обратно местами
                    string readToEnd = exeProc.StandardOutput.ReadToEnd();
                    ConsoleIOSeparatorFix(ref readToEnd, '.', ',');
                    return readToEnd;
                }
                else
                {
                    //Сообщение об ошибке
                    string errMes = _errorDictionary.FindError(errCode);                    
                    throw new Exceptions.RuntimeError(errMes);
                }
            }
            finally
            {
                //return exeProc.StandardOutput.ReadToEnd();
            }
        }

        //"." на "," и обратно
        private void ConsoleIOSeparatorFix(ref string outputStream, char oldSep, char newSep)
        {
            string answerSeparator = outputStream;

            if (answerSeparator.IndexOf(oldSep) != -1)
            {
                while (answerSeparator.IndexOf(oldSep) != -1)
                {
                    answerSeparator = answerSeparator.Replace(oldSep, newSep);
                }
            }
            outputStream = answerSeparator;
        }

        public override void CleanUp()
        {
            File.Delete(_tempPathToCpp);
            File.Delete(_tempPathToExe);
            File.Delete(_tempPathToBat);
            Directory.Delete(_tempDir);
        }
    }
}
